/*
 * Microsecond timer functions for Flying Fox
 *
 * These functions report the time since the system was started,
 * or since the timer was reset.
 *
 * The timer is accurate to microsecond resolution, suitable for
 * timing incoming servo pulses from the R/C receiver. It is 
 * very stable however it's not 100% accurate - its "microseconds" 
 * are actually about 0.9984us. This is close enough for 
 * government work and gives us stability advantages given the
 * available source clock rate.
 *
 * by Zik Saleeba 2008-03-07
 */

#include "ffox.h"
#include "AT91SAM7X256.h"
#include "lib_AT91SAM7X256.h"
#include "timer.h"


/* module variables */
unsigned long Timer_CurrentTime8ms;  /* the number of 8ms intervals since we started */

/* prototypes */
static void Timer_IsrHandler(void) __attribute__ ((naked));


/*
 * NAME:        Timer_Init
 * ACTION:      Initialise the timer subsystem. This sets up timer 0
 *              to do the timing.
 */
 
void Timer_Init( void )
{
    /* set up TIMER0 to 1/3 microsecond counts with 8ms interrupts on "RC" (overflow) */
    AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV2_CLOCK | 
                             AT91C_TC_WAVESEL_UP_AUTO |
                             AT91C_TC_CPCTRG |
                             AT91C_TC_WAVE |
                             AT91C_TC_ASWTRG_CLEAR;
    
    AT91C_BASE_TC0->TC_CV = 0;
    AT91C_BASE_TC0->TC_RC = 48000;           /* the period - 8ms */
    AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
    Timer_CurrentTime8ms = 0;
    
    /* enable timer interrupts */
    AT91C_BASE_TC0->TC_IER = AT91C_TC_CPCS;
    AT91C_BASE_TC0->TC_IDR = AT91C_TC_COVFS | AT91C_TC_LOVRS | AT91C_TC_CPAS | 
            AT91C_TC_CPBS | AT91C_TC_LDRAS | AT91C_TC_LDRBS | AT91C_TC_ETRGS;
            
    AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_TC0, 7, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, &Timer_IsrHandler);
    AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_TC0);
}


/* 
 * NAME:        Timer_Reset
 * ACTION:      Resets the timer to zero
 */
 
void Timer_Reset(void)
{
    AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
    Timer_CurrentTime8ms = 0;
}


/*
 * NAME:        Timer_GetMicroSeconds
 * ACTION:      Get the number of microseconds since the system was
 *              started or the timer was reset. This takes about 
 *              400 days to overflow.
 * RETURNS:     unsigned long long - the number of microseconds since
 *                  the system was started
 */
 
unsigned long long Timer_GetMicroSeconds(void)
{
    return Timer_CookTime(Timer_GetRawTime());
}


/*
 * NAME:        Timer_CookTime
 * ACTION:      Convert a raw time into a more easily digested
 *              microseconds-since-we-started form. This involves
 *              a multiplication and a division so it's best to
 *              avoid using it in time-critical interrupt service
 *              routines.
 * PARAMETERS:  struct RawTime Time - the raw time as 
 *                  returned by Timer_GetRawTime()
 * RETURNS:     unsigned long long - microseconds since we were
 *                  turned on or since Timer_Reset() was called.
 */
 
unsigned long long Timer_CookTime(struct RawTime Time)
{
    return Time.MSBTime * 8000 + Time.LSBTime / 6;
}


/*
 * NAME:        Timer_IsrHandler
 * ACTION:      Interrupt service routine for timer. When the timer
 *              overflows every 8ms it increments the MSB counter.
 */
 
void Timer_IsrHandler(void)
{
	/* Save the context of the interrupted task. */
	portSAVE_CONTEXT();

    if (AT91C_BASE_TC0->TC_SR & AT91C_TC_CPCS)
        Timer_CurrentTime8ms++;        /* increment by 8ms */
    
    /* clear the interrupt in the AIC to complete interrupt handling */ 
    AT91C_BASE_AIC->AIC_EOICR = 0;

	/* Restore the context of whichever task is going to run next. */
	portRESTORE_CONTEXT();
}
